/* Sentinel - IRC Statistical and Operator Services
** main.c - main() and ancilliary functions
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: main.c 7 2010-02-01 19:17:42Z dubkat $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include <signal.h>
#include <locale.h>
#include "event.h"
#include "slog.h"
#include "sutil.h"
#include "match.h"

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

int servsock = -1;
int need_rehash;
time_t CurrentTime;

#ifdef MEMDEBUG
char *malloc_options = "AJX";
#endif

static void start(void);
static void CmdLine(int, char **);
static void PrintVersion();
static void PrintHelp();
static void InitMemCounts(void);

int main(int argc, char *argv[])
{
  int forked = 0;
  FILE *fp;

  /* Seed the time global */
  CurrentTime = time(NULL);

  /* Set up localization of times and dates */
  (void) setlocale(LC_TIME, "");

  /* LC_CTYPE must remain in the "C" locale or else toupper() and
  ** tolower() will be localized.  In the "C" locale, these functions
  ** provide an ASCII casemap.
  **
  ** The LC_* variables default to being in the "C" locale when
  ** a program starts.  Therefore, we do not need an explicit
  ** setlocale(LC_CTYPE, "C");
  */

  /* Some systems default to not dumping core...
  ** we want the cores...
  */
  setup_corefile();

#ifdef USE_ASCII_CASEMAP

  set_match_mapping(MATCH_ASCII);
#endif
  /* libsentinel says MATCH_RFC1459 is the default */

  me.cmdflags = 0;
  need_rehash = 0;

  CmdLine(argc, argv);

  init_logsystem();
  init_log(DEFAULT_LOG, LOGFILE, L_NOTICE, BACKUPLOGFMT);
#ifdef SPLITSERV

  init_log(SPLIT_LOG, SPLITLOG, L_NOTICE, BACKUPSPLOGFMT);
#endif
#if defined(MONSERV) && defined(MON_DRONES)

  init_log(DRONE_LOG, DRONELOG, L_NOTICE, BACKUPDRONEFMT);
  set_logts(DRONE_LOG, TRUE);
#endif

  init_log(SPY_LOG, SPYLOG, L_NOTICE, BACKUPSPYFMT);
#ifdef IO_LOG

  init_log(IO_LOGNO, IOLOGFILE, L_NOTICE, BACKUPIOFMT);
#endif

  slog(DEFAULT_LOG, L_CRIT, "Statistics Started (%s %s).", stats_version,
       VersionFlags());
  me.t_start = CurrentTime;

  /* chdir returns 0 on sucess, -1 on failure */
  if (chdir(DPATH) != 0)
  {
    fprintf(stderr, "Unable to chdir to DPATH: %s\n", DPATH);
    fprintf(stderr, "Fatal Error, exiting\n");
    close_logfiles();
    exit(1);
  }
  settings.pflags = 0;
  me.max_fd = 2; /* stderr */

  InitDefaults();
  InitLists();
  InitMemCounts();
  ConfLoad();
  VerifyConf();
  InitMessageStrings();
  InitMsgHash();
  InitDCCHash();
  InitHTMLArray();

  me.capabs = generate_capab_string();

  /* This should ONLY be run once */
  srandom(CurrentTime);

  /* Zero out the byte counters */
  netinfo.inb = 0;
  netinfo.outb = 0;
#ifdef DIE_ON_MASS_COLLIDE

  netinfo.collides = 0;
#endif

  netinfo.connections = 0;
  daily.connections = 0;

  LoadStats();
  LoadTLD();

  eventInit();

  if (!IsDebug() && !IsNoFork())
    forked = fork();
  if (forked)
  {
    fp = fopen(PIDFILE, "w");
    fprintf(fp, "%i", forked);
    fclose(fp);
    return 0;
  }

  if (IsDebug() || IsNoFork())
  {
    fp = fopen(PIDFILE, "w");
    fprintf(fp, "%i", (int)getpid());
    fclose(fp);
  }

  if (!IsDebug() && !IsNoFork())
  {
    freopen("/dev/null", "r", stdin);
    freopen("/dev/null", "w", stdout);
    freopen("stderr.log", "a", stderr);
  }

#ifdef SPLITSERV
  SpInitTables();
#endif

#if defined(SPLITSERV)

  is_delayed = 1;
#endif
#if defined(SPYMODE) || defined(ALERTMODE) || defined(DRONES_CHANNEL)

  ad_delayed = 1;
#endif
#if defined(TRACK_CTCPVERSION)

  mn_delayed = 1;
#endif

  /* Setup signals */
  signal(SIGTERM, SigTerm);
  signal(SIGHUP, SigHup);
  signal(SIGUSR1, SigUsr1);
  signal(SIGINT, SigInt);

  /* On FreeBSD at least, gdb will cause a process to core
  ** when you detach the debugger from a running Sentinel.
  ** This prevents that from happening.  Reasoning?
  **
  ** http://www.freebsd.org/cgi/query-pr.cgi?pr=35175
  */
  signal(SIGTRAP, SIG_IGN);

  /* I've been told that SIGPIPE can occur on sockets, just for
  ** safety, I'll ignore them.
  */
  signal(SIGPIPE, SIG_IGN);

  setsid();

  /* The first time, the connect has to be started before read_loop()
  ** is called.
  */
  ConnectTo(settings.uplink, settings.port);

  slog(DEFAULT_LOG, L_CRIT, "Sentinel started successfully");

  netinfo.users = netinfo.opers = netinfo.servers = netinfo.channels = 0;
  netinfo.invis = 0;

  while (TRUE)
  {
    start();
  }

  /* NOT REACHED */
  return 1;
}

static void start()
{
  init_server_hash();
#ifdef SPLITSERV
  map_head = SpNewServer(settings.name, NULL);
#endif
  init_user_hash();
  init_channel_hash();
#ifdef MONSERV

  init_host_hashes();
#endif

  init_tld();

  slog(DEFAULT_LOG, L_NOTICE, "Connecting to %s:%d", settings.uplink, settings.port);
  if (servsock > 0)
  {
    close(servsock);
    delete_connection(servsock);
  }

  read_loop();
}

void ServTermination(void)
{
  FlushEvents();

  slog(DEFAULT_LOG, L_NOTICE, "Reconnecting to the server in %d seconds",
       settings.r_time);
  /* Here I need to flush the server/user table */
  if (me.uplink != NULL)
  {
    FlushServer(me.uplink);
    me.uplink = NULL;
#if defined(IRCD_HYB7) || defined(IRCD_BAHAMUT) || defined(IRCD_HYBSVS)

    UnSetEOB();
#endif

  }
  else
  {
    debug("Invalid me.uplink");
  }
  /* To prevent multiple operlist entries, delete the services
  ** users so they are recreated fresh
  */
  DelUserU(AdminServ.u);
  AdminServ.u = NULL;
#ifdef SPLITSERV

  DelUserU(SplitServ.u);
  SplitServ.u = NULL;
#endif
#ifdef STATSERV

  DelUserU(StatServ.u);
  StatServ.u = NULL;
#endif
#ifdef JUPESERV

  DelUserU(JupeServ.u);
  JupeServ.u = NULL;
#endif
#ifdef LISTSERV
  DelUserU(ListServ.u);
  ListServ.u = NULL;
#endif
#ifdef MONSERV

  DelUserU(MonServ.u);
  MonServ.u = NULL;
#endif

  if (eventFind(evh_ConnectServer, NULL) == -1)
    eventAdd("Server Connect", evh_ConnectServer, NULL, settings.r_time);
  terminate_read_loop = 1;
#if defined(SPLITSERV)
  /* Reset the variables used in initialization to prevent the
  ** service from spewing large chunks of data out to the channel
  */
  is_delayed = 1;
#endif
#if defined(SPYMODE) || defined(ALERTMODE) || defined(DRONES_CHANNEL)

  ad_delayed = 1;
#endif
#if defined(TRACK_CTCPVERSION)

  mn_delayed = 1;
#endif
#ifdef SPLITSERV
  SpDelServer(settings.name);
  map_head = NULL;
#endif
  DelServerS(me.s);
  me.s = NULL;
}

static void CmdLine(int argc, char *argv[])
{
  char c;

  while ( (c = getopt(argc, argv, "dvhn")) != -1)
  {
    switch (c)
    {
      case 'd':
        me.cmdflags |= CMD_DEBUG;
        set_logdebug(TRUE);
        break;
      case 'v':
        PrintVersion();
        exit(0);
        /* NOT REACHED */
        break;
      case 'h':
        PrintHelp();
        exit(0);
        /* NOT REACHED */
        break;
      case 'n':
        me.cmdflags |= CMD_NOFORK;
        break;
    }
  }
}

static void PrintVersion()
{
  printf("Sentinel version %s\nSPATH: %s\n", stats_version,
         SPATH);
  printf("Sentinel NOT Started.\n");
}

static void PrintHelp()
{
  printf("sentinel [-n|-d|-h|-v]\n-n Do not fork into the background\n");
  printf("-d Set debugging mode\n-h Print this help screen\n");
  printf("-v Print the version and exit\n");
  printf("Sentinel NOT Started.\n");
}

static void InitMemCounts(void)
{
  memset(&memcounts, 0, sizeof(memcounts));
}
